package nachos.threads;

import nachos.machine.*;

/**
 * A <i>communicator</i> allows threads to synchronously exchange 32-bit
 * messages. Multiple threads can be waiting to <i>speak</i>,
 * and multiple threads can be waiting to <i>listen</i>. But there should never
 * be a time when both a speaker and a listener are waiting, because the two
 * threads can be paired off at this point.
 */
public class Communicator {
    /**
     * Allocate a new communicator.
     */
    public Communicator() {
      ///////////////// BEGIN HENRI /////////////////
      this.speakerHasArrived = false;
      this.listenerHasArrived = false;
      this.thereIsAWordToBeConsumed = false;

      this.mutex = new Lock();

      this.listenerHasArrivedCond = new Condition(mutex);
      this.speakerHasLeftCond = new Condition(mutex);
      this.listenerHasLeftCond = new Condition(mutex);
      this.wordHasBeenConsumedCond = new Condition(mutex);
      this.wordIsReadyToBeConsumedCond = new Condition(mutex);

      ///////////////// END HENRI /////////////////
    }

    /**
     * Wait for a thread to listen through this communicator, and then transfer
     * <i>word</i> to the listener.
     *
     * <p>
     * Does not return until this thread is paired up with a listening thread.
     * Exactly one listener should receive <i>word</i>.
     *
     * @param	word	the integer to transfer.
     */
    public void speak(int word) {

    ///////////////// BEGIN HENRI /////////////////
	/* Enter the critical section */
	mutex.acquire();

	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Speaker waiting for other speakers to be departed...");
        while (speakerHasArrived) {
          speakerHasLeftCond.sleep();
        }	
	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Speaker is in!");
	speakerHasArrived = true;
	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Speaker waiting for a listener to arrive...");
	while (!listenerHasArrived) {
          listenerHasArrivedCond.sleep();
        }
	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "A listener has arrived, and I am producing my word");
	/* store the word */
	this.word = word;
	/* signals that it is ready to be consumed */
	thereIsAWordToBeConsumed = true;
	wordIsReadyToBeConsumedCond.wake();
	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Waiting for the word to be consumed by listener...");
  	while (thereIsAWordToBeConsumed) {
          wordHasBeenConsumedCond.sleep();
        }
	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Word has been consumed, leaving...");
	/* signal that the speaker is leaving */
	speakerHasArrived = false;
	speakerHasLeftCond.wake();

	/* leave the critical section */
	mutex.release();
    ///////////////// END HENRI /////////////////

    }

    /**
     * Wait for a thread to speak through this communicator, and then return
     * the <i>word</i> that thread passed to <tt>speak()</tt>.
     *
     * @return	the integer transferred.
     */    
    public int listen() {
    ///////////////// BEGIN HENRI /////////////////
       int word;

	/* enter the critical section */
        mutex.acquire();

	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Listener waiting for other listeners to be departed...");
        while (listenerHasArrived) {
          listenerHasLeftCond.sleep();
        }
	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Listener is in!");

	/* signal that a listener has arrived */
        listenerHasArrived = true;
        listenerHasArrivedCond.wake();

	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Listener waiting for a word to be produced...");
        while (!thereIsAWordToBeConsumed) {
          wordIsReadyToBeConsumedCond.sleep();
        }

	/* consume the word and signal */
        word = this. word;
        thereIsAWordToBeConsumed = false;
        wordHasBeenConsumedCond.wake();

	/* signal that the listener is leaving */
        listenerHasArrived = false;
        listenerHasLeftCond.wake();
	Lib.debug(dbgCommunicator, KThread.currentThread().toString() + 
                  "Listener got the word, leaving...");

	/* exit the critical section */
        mutex.release();

    ///////////////// END HENRI /////////////////
	return word;
    }

    /**
     * Tests whether this module is working.
     */
    public static void selfTest() {
        CommunicatorTest.runTest();
    }
 
    ///////////////// BEGIN HENRI /////////////////

    private static final char dbgCommunicator = 'k';

    /* The word that's beeing communicated in this Communicator */
    private int word;

    /* Useful booleans */
    private boolean speakerHasArrived;		// A speaker's in
    private boolean listenerHasArrived;		// A listener's in
    private boolean thereIsAWordToBeConsumed;	// A word's waiting

    /* Lock for mutual exclusion */
    private Lock mutex;

    /* Useful Condition Variables */
    private Condition listenerHasArrivedCond;
    private Condition speakerHasLeftCond;
    private Condition listenerHasLeftCond;
    private Condition wordHasBeenConsumedCond;
    private Condition wordIsReadyToBeConsumedCond;
    ///////////////// END HENRI /////////////////

}
